//  Copyright 2009 David Ekholm, Jalbum AB
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package net.jalbum.editor;
// ***** new project hosting space! *****
package net.jalbum.editor;

import edu.stanford.ejalbert.BrowserLauncher;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSeparator;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import net.jalbum.io.PCWriter;
import org.fife.io.UnicodeReader;
import org.fife.ui.autocomplete.*;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.fife.ui.rtextarea.SearchEngine;
import se.datadosen.component.JStackLabel;
import se.datadosen.component.ToggleAction;
import se.datadosen.util.IO;
import se.datadosen.util.StringCodec;

/**
 * Simple and flexible text editor. Core functionality mimics Microsoft's Notepad
 * Features syntax highlightning, code completion, printing, drag and drop support,
 * code indentation and regular expression search & replace.
 * Most features are implemented by Robert Futrell's fantastic RSyntaxTextArea and
 * AutoCompletion components. Many thanks to Robert for his work and support for
 * these high class components!
 * 
 * @author david
 * mods @TomCee
 */
public class JTextPad extends JFrame {

    static final int menuShortcutKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); // Convenience
    public final static String APP_NAME = "Jalbum Textpad";
    public final static String VERSION = "1.2";
    JFileChooser fileChooser = new JFileChooser();
    JTabbedPane tabbedPane = new JTabbedPane();
    private JMenuBar menuBar = new JMenuBar();
    private JMenu fileMenu;
    JRadioButtonMenuItem pcTerminationStyle = new JRadioButtonMenuItem("PC style");
    JRadioButtonMenuItem unixTerminationStyle = new JRadioButtonMenuItem("UNIX style");
    ButtonGroup terminationStyle = new ButtonGroup();

    {
        terminationStyle.add(pcTerminationStyle);
        terminationStyle.add(unixTerminationStyle);
        pcTerminationStyle.setSelected(true);
    }
    RSyntaxTextArea textArea;
    JPanel textPanel;
    RTextScrollPane textScroller;
    int textHash = 0;
    static Map<String, RSyntaxTextArea> textComps = new HashMap<String, RSyntaxTextArea>();
    static Map<String, Integer> textHashes = new HashMap<String, Integer>();
    static Map<Integer, File> tabToFileName = new HashMap<Integer, File>();
    public static Map<String, String> fileTypes = new HashMap<String, String>();

    static {
        fileTypes.put("txt", SyntaxConstants.SYNTAX_STYLE_NONE);
        fileTypes.put("jsp", SyntaxConstants.SYNTAX_STYLE_JSP);
        fileTypes.put("htt", SyntaxConstants.SYNTAX_STYLE_JSP);
        fileTypes.put("inc", SyntaxConstants.SYNTAX_STYLE_JSP);
        fileTypes.put("java", SyntaxConstants.SYNTAX_STYLE_JAVA);
        fileTypes.put("bsh", SyntaxConstants.SYNTAX_STYLE_JAVA);
        fileTypes.put("css", SyntaxConstants.SYNTAX_STYLE_CSS);
        fileTypes.put("xml", SyntaxConstants.SYNTAX_STYLE_XML);
        fileTypes.put("info", SyntaxConstants.SYNTAX_STYLE_XML);
        fileTypes.put("rss", SyntaxConstants.SYNTAX_STYLE_XML);
        fileTypes.put("html", SyntaxConstants.SYNTAX_STYLE_HTML);
        fileTypes.put("htm", SyntaxConstants.SYNTAX_STYLE_HTML);
        fileTypes.put("xhtml", SyntaxConstants.SYNTAX_STYLE_HTML);
        fileTypes.put("properties", SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE);
        fileTypes.put("jap", SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE);
        fileTypes.put("js", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT);
    }
    JStackLabel statusBar = new JStackLabel();
    private boolean isNewDocument = false;
    private boolean fileChanged = false;
    //  **************************
    Action newAction = new AbstractAction("New") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            newFile();
        }
    };
    Action openAction = new AbstractAction("Open...") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            JFileChooser chooser = new JFileChooser();
            chooser.setCurrentDirectory(fileChooser.getCurrentDirectory());
            chooser.setSelectedFile(fileChooser.getSelectedFile());
            chooser.setMultiSelectionEnabled(true);
            int choice = chooser.showOpenDialog(JTextPad.this);
            File[] files = chooser.getSelectedFiles();
            if (choice == JFileChooser.APPROVE_OPTION) {
                isNewDocument = false;
                for (File f : files) {
                    if (!textHashes.containsKey(f.getAbsolutePath())) {
                        openFile(f);
                    } else {
                        //don't open same file twice
                        JOptionPane.showMessageDialog(rootPane, "File " + f.getName()
                                + " already opened.");
                    }
                }
                tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
                String s = tabToFileName.get(tabbedPane.getSelectedIndex()).getAbsolutePath();
                textArea = textComps.get(s);
            }
        }
    };

    private void openFile(File file) {
        textArea = new RSyntaxTextArea(60, 120) {

            {
                setLineWrap(true);
                setWrapStyleWord(true);
            }
        };

        try {
            if (!isNewDocument) {
                byte[] raw = IO.readBytes(file);
                StringCodec sc = new StringCodec();
                String tmp = sc.decode(raw);
                if (tmp.indexOf('\r') >= 0) {
                    pcTerminationStyle.setSelected(true);
                } else {
                    unixTerminationStyle.setSelected(true);
                }
                String encoding = sc.getDetectedEncoding();
                StringBuilder buf = new StringBuilder();
                BufferedReader reader = new BufferedReader(new UnicodeReader(new ByteArrayInputStream(raw), encoding));
                String line;
                while ((line = reader.readLine()) != null) {
                    buf.append(line);
                    buf.append('\n');
                }
                String text = buf.toString();
                fileChooser.setSelectedFile(file);
                getRootPane().putClientProperty("Window.documentFile", file); // Mac OS X
                setSyntaxStyle();
                textArea.setText(text);

            }
            textArea.setCaretPosition(0);
            textArea.discardAllEdits();
            textScroller = new RTextScrollPane(800, 500, textArea, true);
            getRootPane().putClientProperty("Window.documentModified", Boolean.FALSE);
            textHash = textArea.getText().hashCode();
            textHashes.put(file.getAbsolutePath(), textHash);

            textArea.addCaretListener(new javax.swing.event.CaretListener() {

                public void caretUpdate(javax.swing.event.CaretEvent evt) {
                    textArea.requestFocusInWindow();
                    String s = tabToFileName.get(tabbedPane.getSelectedIndex()).getAbsolutePath();
                    textComps.put(s, textArea);
                    checkForChanges(s);
                    updateStatusBar();
                }
            });
            textComps.put(file.getAbsolutePath(), textArea);
            tabbedPane.addTab(file.getName(), new RTextScrollPane(800, 500,
                    textComps.get(file.getAbsolutePath()), true));
            tabToFileName.put(tabbedPane.getTabCount() - 1, file);
            tabbedPane.setToolTipTextAt(tabbedPane.getTabCount() - 1,
                    file.getAbsolutePath());
            tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
            updateTitle();
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(JTextPad.this,
                    "Can't open file " + file.getName() + ": " + ex.getMessage(),
                    APP_NAME,
                    JOptionPane.ERROR_MESSAGE);
        }
    }
    Action closeAction = new AbstractAction("Close") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_W, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            String s = tabToFileName.get(tabbedPane.getSelectedIndex()).getAbsolutePath();
            checkForChanges(s);
            if (fileChanged) {
                saveAsAction.actionPerformed(null);
            }
            int tmp = tabbedPane.getSelectedIndex();
            tabbedPane.remove(tabbedPane.getSelectedComponent());
            //rebuild the tabToFileName map
            for (int i = tabbedPane.getSelectedIndex() + 1; i <= tabbedPane.getTabCount(); i++) {
                tabToFileName.put(i - 1, tabToFileName.get(i));
            }
            tabbedPane.setSelectedIndex(tmp);
            fileChooser.setSelectedFile(tabToFileName.get(tmp));
            //remove the last tab reference, as it is not used anymore
            tabToFileName.remove(tabbedPane.getTabCount());
        }
    };
    Action closeAllAction = new AbstractAction("Close All") {

        public void actionPerformed(ActionEvent e) {
        }
    };
    Action saveAction = new AbstractAction("Save") {

        {
            putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, menuShortcutKeyMask));
        }

        public void actionPerformed(ActionEvent e) {
            File workingFile = getWorkingFile();
            if (workingFile == null) {
                saveAsAction.actionPerformed(e);
                // do I need this anymore ?????????
            } else {
                saveFile(workingFile);
                textHash = textArea.getText().hashCode();
                textHashes.put(workingFile.getAbsolutePath(), textHash);
            }
        }
    };
    Action saveAsAction = new AbstractAction("Save As...") {

        public void actionPerformed(ActionEvent e) {
            int choice = fileChooser.showSaveDialog(JTextPad.this);
            File selectedFile = fileChooser.getSelectedFile();
            if (choice == JFileChooser.APPROVE_OPTION && selectedFile != null) {
                if (selectedFile.exists()) {
                    int answer = JOptionPane.showConfirmDialog(JTextPad.this,
                            "" + selectedFile.getAbsolutePath() + " already exists.\nDo you wish to replace it?",
                            "Save As",
                            JOptionPane.YES_NO_CANCEL_OPTION,
                            JOptionPane.WARNING_MESSAGE);
                    if (answer == JOptionPane.YES_OPTION) {
                        saveFile(selectedFile);
                        updateMaps(selectedFile);
                        return;
                    }
                    if (answer == JOptionPane.NO_OPTION) {
                        return;
                    }
                }
                saveFile(selectedFile);
                updateMaps(selectedFile);
                JOptionPane.showMessageDialog(rootPane, selectedFile.getName());
                return;
            }
            throw new OperationCanceledException();
        }
    };

    private void updateMaps(File selectedFile) {
        String s = tabToFileName.get(tabbedPane.getSelectedIndex()).getAbsolutePath();
        textComps.remove(s);
        textHashes.remove(s);
        tabToFileName.put(tabbedPane.getSelectedIndex(), selectedFile);
        textComps.put(selectedFile.getAbsolutePath(), textArea);
        textHashes.put(selectedFile.getAbsolutePath(), textArea.getText().hashCode());
        tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), selectedFile.getName());
    }

    private void writeTextFile(String content, File file, String encoding)
            throws IOException {
        Writer writer = new OutputStreamWriter(new FileOutputStream(file), encoding);
        if (pcTerminationStyle.isSelected()) {
            writer = new PCWriter(writer);
        }
        writer.write(content);
        writer.close();
    }

    private void saveFile(File file) {
        try {
            String text = textArea.getText();
            writeTextFile(text, file, "UTF-8");
            getRootPane().putClientProperty("Window.documentModified", Boolean.FALSE);
            fileChooser.setSelectedFile(file);
            setSyntaxStyle();
            updateTitle();
            statusBar.setText("Document written to " + file.getAbsolutePath());
            getRootPane().putClientProperty("Window.documentFile", file); // Mac OS X
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(JTextPad.this,
                    "Can't write to file " + file.getName() + ": " + ex.getMessage(),
                    APP_NAME,
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    private void checkForChanges(String key) {
        int oldHash = textHashes.get(key);
        int newHash = textArea.getText().hashCode();
        if (oldHash != newHash) {
            fileChanged = true;
        } else {
            fileChanged = false;
        }
    }

    private void newFile() {
        String newDocName = null;
        isNewDocument = true;
        int i;
        File f = null;
        for (i = 1; i < 99; i++) {
            newDocName = "document" + String.valueOf(i);
            f = new File(getCurrentDirectory(), newDocName);
            if (!f.exists() && !textHashes.containsKey(f.getAbsolutePath())) {
                openFile(f);
                break;
            }
        }
    }

    public void setSyntaxStyle() {
        String ext = IO.extensionOf(getWorkingFile()).toLowerCase();
        String syntaxStyle = fileTypes.get(ext);
        if (syntaxStyle == null) {
            syntaxStyle = SyntaxConstants.SYNTAX_STYLE_NONE;
        }
        textArea.setSyntaxEditingStyle(syntaxStyle);
        updateStatusBar();
    }

    private void updateTitle() {
        String s = tabToFileName.get(tabbedPane.getSelectedIndex()).getAbsolutePath();
        textArea = textComps.get(s);
        String title = APP_NAME + " [" + s + "]";
        setTitle(title);
    }

    private void updateStatusBar() {
        int line = textArea.getCaretLineNumber() + 1;
        int col = textArea.getCaretOffsetFromLineStart() + 1;
        String style = pcTerminationStyle.isSelected() ? "PC" : "UNIX";
        statusBar.setText("Mode " + textArea.getSyntaxEditingStyle()
                + " | UTF-8 | " + style + " | Ln " + line + ", Col " + col
                + " | Changed = " + fileChanged);
    }

    public File getWorkingFile() {
        return fileChooser.getSelectedFile();
    }

    private String getDocumentTitle() {
        File wf = getWorkingFile();
        if (wf == null) {
            return "Untitled";
        }
        String title = "";
        File parent = wf.getParentFile();
        if (parent != null) {
            title = parent.getName() + File.separatorChar;
        }
        title += wf.getName();
        return title;
    }

    public File getCurrentDirectory() {
        return fileChooser.getCurrentDirectory();
    }

    public void setCurrentDirectory(File dir) {
        fileChooser.setCurrentDirectory(dir);
    }

    public JTextPad() {
        initComponents();
    }

    /**
     * Edit passed file, or if a directory, set current directory to it
     * @param workingFile
     */
    public JTextPad(File workingFile) {
        initComponents();
        if (workingFile.isDirectory()) {
            setCurrentDirectory(workingFile);
        } else {
            setCurrentDirectory(workingFile.getParentFile());
            openFile(workingFile);
        }
    }

    private void initComponents() {
// tmp settings ?
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        newFile();
        getContentPane().add(tabbedPane);
        // tmp end
        // Menus
        fileMenu = new JMenu("File");

        fileMenu.add(new JMenuItem(newAction));
        fileMenu.add(new JMenuItem(openAction));
        fileMenu.add(new JSeparator());
        fileMenu.add(new JMenuItem(closeAction));
        fileMenu.add(new JMenuItem(closeAllAction));
        fileMenu.add(new JSeparator());
        fileMenu.add(new JMenuItem(saveAction));
        fileMenu.add(new JMenuItem(saveAsAction));
        fileMenu.add(new JSeparator());

        menuBar.add(fileMenu);
        if (!Main.isMac() || !Main.runsStandalone) {
            //menuBar.add(helpMenu);
        }
        setJMenuBar(menuBar);
        {
            ActionListener terminationStyleListner = new ActionListener() {

                public void actionPerformed(ActionEvent e) {
                    updateStatusBar();
                }
            };
            pcTerminationStyle.addActionListener(terminationStyleListner);
            unixTerminationStyle.addActionListener(terminationStyleListner);
        }
        JPanel southPanel = new JPanel(new BorderLayout());
        southPanel.setBorder(BorderFactory.createEmptyBorder(0, 6, 2, 6));
        southPanel.add(statusBar, BorderLayout.BEFORE_LINE_BEGINS);
        southPanel.add(new JLabel(" "), BorderLayout.AFTER_LINE_ENDS);
        statusBar.setText("no text");
        add(southPanel, BorderLayout.SOUTH);

        tabbedPane.addChangeListener(new javax.swing.event.ChangeListener() {

            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                String s = tabToFileName.get(tabbedPane.getSelectedIndex()).getAbsolutePath();
                fileChooser.setSelectedFile(new File(s));
                textArea = textComps.get(s);
                textArea.requestFocusInWindow();
                checkForChanges(s);
                updateTitle();
                updateStatusBar();

            }
        });

        pack();
    }

    private void message(String s) {
        JOptionPane.showMessageDialog(rootPane, s);
    }
}//

